﻿/*
 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
 */

/**
 * @fileOverview Defines the {@link CKEDITOR.scriptLoader} object, used to load scripts
 *        asynchronously.
 */

/**
 * Load scripts asynchronously.
 * @namespace
 * @example
 */
CKEDITOR.scriptLoader = (function () {
    var uniqueScripts = {},
        waitingList = {};

    return /** @lends CKEDITOR.scriptLoader */ {
        /**
         * Loads one or more external script checking if not already loaded
         * previously by this function.
         * @param {String|Array} scriptUrl One or more URLs pointing to the
         *        scripts to be loaded.
         * @param {Function} [callback] A function to be called when the script
         *        is loaded and executed. If a string is passed to "scriptUrl", a
         *        boolean parameter is passed to the callback, indicating the
         *        success of the load. If an array is passed instead, two array
         *        parameters are passed to the callback; the first contains the
         *        URLs that have been properly loaded, and the second the failed
         *        ones.
         * @param {Object} [scope] The scope ("this" reference) to be used for
         *        the callback call. Default to {@link CKEDITOR}.
         * @param {Boolean} [showBusy] Changes the cursor of the document while
         +         *        the script is loaded.
         * @example
         * CKEDITOR.scriptLoader.load( '/myscript.js' );
         * @example
         * CKEDITOR.scriptLoader.load( '/myscript.js', function( success )
         *     {
		 *         // Alerts "true" if the script has been properly loaded.
		 *         // HTTP error 404 should return "false".
		 *         alert( success );
		 *     });
         * @example
         * CKEDITOR.scriptLoader.load( [ '/myscript1.js', '/myscript2.js' ], function( completed, failed )
         *     {
		 *         alert( 'Number of scripts loaded: ' + completed.length );
		 *         alert( 'Number of failures: ' + failed.length );
		 *     });
         */
        load: function (scriptUrl, callback, scope, showBusy) {
            var isString = ( typeof scriptUrl == 'string' );

            if (isString)
                scriptUrl = [scriptUrl];

            if (!scope)
                scope = CKEDITOR;

            var scriptCount = scriptUrl.length,
                completed = [],
                failed = [];

            var doCallback = function (success) {
                if (callback) {
                    if (isString)
                        callback.call(scope, success);
                    else
                        callback.call(scope, completed, failed);
                }
            };

            if (scriptCount === 0) {
                doCallback(true);
                return;
            }

            var checkLoaded = function (url, success) {
                ( success ? completed : failed ).push(url);

                if (--scriptCount <= 0) {
                    showBusy && CKEDITOR.document.getDocumentElement().removeStyle('cursor');
                    doCallback(success);
                }
            };

            var onLoad = function (url, success) {
                // Mark this script as loaded.
                uniqueScripts[url] = 1;

                // Get the list of callback checks waiting for this file.
                var waitingInfo = waitingList[url];
                delete waitingList[url];

                // Check all callbacks waiting for this file.
                for (var i = 0; i < waitingInfo.length; i++)
                    waitingInfo[i](url, success);
            };

            var loadScript = function (url) {
                if (uniqueScripts[url]) {
                    checkLoaded(url, true);
                    return;
                }

                var waitingInfo = waitingList[url] || ( waitingList[url] = [] );
                waitingInfo.push(checkLoaded);

                // Load it only for the first request.
                if (waitingInfo.length > 1)
                    return;

                // Create the <script> element.
                var script = new CKEDITOR.dom.element('script');
                script.setAttributes({
                    type: 'text/javascript',
                    src: url
                });

                if (callback) {
                    if (CKEDITOR.env.ie) {
                        // FIXME: For IE, we are not able to return false on error (like 404).

                        /** @ignore */
                        script.$.onreadystatechange = function () {
                            if (script.$.readyState == 'loaded' || script.$.readyState == 'complete') {
                                script.$.onreadystatechange = null;
                                onLoad(url, true);
                            }
                        };
                    }
                    else {
                        /** @ignore */
                        script.$.onload = function () {
                            // Some browsers, such as Safari, may call the onLoad function
                            // immediately. Which will break the loading sequence. (#3661)
                            setTimeout(function () {
                                onLoad(url, true);
                            }, 0);
                        };

                        // FIXME: Opera and Safari will not fire onerror.

                        /** @ignore */
                        script.$.onerror = function () {
                            onLoad(url, false);
                        };
                    }
                }

                // Append it to <head>.
                script.appendTo(CKEDITOR.document.getHead());

                CKEDITOR.fire('download', url);		// @Packager.RemoveLine
            };

            showBusy && CKEDITOR.document.getDocumentElement().setStyle('cursor', 'wait');
            for (var i = 0; i < scriptCount; i++) {
                loadScript(scriptUrl[i]);
            }
        }
    };
})();
